#include "FileOperations.h"
#include <iostream>
#include <time.h>
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	#include <windows.h>
	#include <io.h>
#else
	#include <unistd.h>
	#include <sys/stat.h>
	#include <sys/types.h>
	#include <utime.h>
	#include <dirent.h>
	#include <stdlib.h>
	#include <string.h>
#endif

bool FileOperations::FileDelete(const gstring& file)
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	return DeleteFile(file.c_str());	
	
#else
	return unlink(file.c_str()) == 0;
#endif
	
}
bool FileOperations::DirectoryDelete(const gstring& directory)
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	return RemoveDirectory(directory.c_str());
#else
	return rmdir(directory.c_str()) == 0;
#endif
}

bool FileOperations::DirectoryCreate(const gstring& directory)
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	return CreateDirectory(directory.c_str(), NULL);
#else
	return mkdir(directory.c_str(), 0777) == 0;
#endif
}

gchar FileOperations::DirectorySeparator()
{
	gchar separator;
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	separator = '\\';
#else 
	separator = '/';
#endif
	return separator;
}

void FileOperations::Print(INT64 n)
{
	if (n < 0) {
		cout << "-";
		Print(-n);
		return;
	}
	if (n < 1000) {
		cout << (int)n;
		return;
	}
	Print(n / 1000);
	printf(",%03d", (int)(n % 1000));
}

void FileOperations::Print(const gstring& output)
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	printf("%ws", output.c_str());
#else
	printf("%s", output.c_str());
#endif
}

bool FileOperations::DoesFolderExist(const gstring& folder)
{
	FileAttributes attributes;
	bool exists = attributes.GetAttributes(folder);
	if (exists)
	{
		exists = attributes.IsDirectory();
	}
	return exists;
}

bool FileOperations::AreFileNamesCaseSensitive()
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	return false;
#else 
	return true;
#endif
}

void FileOperations::ReadDirectory(const gstring& directory, DirectoryListing* directoryListing)
{
	DirectoryEntry* entry;
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	gstring directoryWithWildCards;
	directoryWithWildCards.reserve(directory.size() + 6);
	DirectoryListing::CombineFileNames(directory, TEXT("*.*"), directoryWithWildCards);
	struct _wfinddatai64_t oFileInfo;
	intptr_t oHandle;
	bool bContinue;

	oHandle = _wfindfirsti64(directoryWithWildCards.c_str(), &oFileInfo);
	bContinue = (oHandle != -1);
	while (bContinue)
	{
		if ((lstrcmpW(oFileInfo.name, TEXT(".")) != 0) && (lstrcmpW(oFileInfo.name, TEXT("..")) != 0))
		{
			entry = directoryListing->AddFile(oFileInfo.name, oFileInfo.size, oFileInfo.time_write);
			entry->dwFileAttributes = oFileInfo.attrib;
		}
		bContinue = (_wfindnexti64(oHandle, &oFileInfo) != -1);
	}
	if (oHandle != -1)
	{
		_findclose(oHandle);
	}
#else
	DIR* dir;
	struct dirent* ent;	
	struct stat filestatus;	
	gstring tempPath;
	gstring tempFile;
	
	dir = opendir(directory.c_str());
	if (dir != NULL)
	{
		while ((ent = readdir(dir)) != NULL)
		{
			if ((strcmp(ent->d_name, ".") == 0) || (strcmp(ent->d_name, "..") == 0))
			{
				continue;
			}
			// Get the file size and last modified date.
			tempFile = ent->d_name;
			DirectoryListing::CombineFileNames(directory, tempFile, tempPath);	
			stat(tempPath.c_str(), &filestatus);
			// Add the file to the list.
			entry = directoryListing->AddFile(ent->d_name, filestatus.st_size, filestatus.st_mtime);
			entry->d_type = ent->d_type;
		}
		closedir(dir);
	}
#endif
}

bool FileOperations::CopyTheFile(const gstring& sourceFullName, const gstring& targetFullName, int speed)
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	HANDLE sourcefile;
	HANDLE targetfile;
	const DWORD blockOfMemorySize = 4096;
	byte blockOfMemory[blockOfMemorySize];
	DWORD howManyBytesWereRead;
	DWORD howManyBytesWereWritten;
	FILETIME createTime;
	FILETIME lastAccessTime;
	FILETIME lastWriteTime;
	const int FastestSpeed = 100;
	if (speed == FastestSpeed)
	{
		return CopyFile(sourceFullName.c_str(), targetFullName.c_str(), false);
	}	
	sourcefile = CreateFileW(sourceFullName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (sourcefile == INVALID_HANDLE_VALUE) {
		return false;
	}
	targetfile = CreateFileW(targetFullName.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (targetfile == INVALID_HANDLE_VALUE) {
		CloseHandle(sourcefile);
		return false;
	}
	while (true) {
		if (0 == ReadFile(sourcefile, blockOfMemory, blockOfMemorySize, &howManyBytesWereRead, NULL))
		{
			// Error reading.
			GetErrorMessage(GetLastError(), (LPTSTR)blockOfMemory, blockOfMemorySize / 2);
			printf("error %ws reading from file %ws.\n", (LPTSTR)blockOfMemory, sourceFullName.c_str());
			CloseHandle(sourcefile);
			CloseHandle(targetfile);
			return false;
		}
		if (howManyBytesWereRead == 0)
		{
			// End of file.
			break;
		}
		Sleep(99 - speed);
		if (0 == WriteFile(targetfile, blockOfMemory, howManyBytesWereRead, &howManyBytesWereWritten, NULL))
		{
			// error writing.
			GetErrorMessage(GetLastError(), (LPTSTR)blockOfMemory, blockOfMemorySize / 2);
			printf("error %ws writing to file %ws.\n", (LPTSTR)blockOfMemory, targetFullName.c_str());
			CloseHandle(sourcefile);
			CloseHandle(targetfile);
			return false;
		}
		Sleep(FastestSpeed - 1 - speed);
	}
	// read the file time from the source file
	if (GetFileTime(sourcefile, &createTime, &lastAccessTime, &lastWriteTime))
	{
		// write the file time to the target file.
		SetFileTime(targetfile, &createTime, &lastAccessTime, &lastWriteTime);
	}

	CloseHandle(sourcefile);
	CloseHandle(targetfile);
#else
	const int BufferSize = 4096;
	gchar buffer[BufferSize];
	FILE* finput;
	FILE* foutput;
	int bytesRead;
	int bytesWritten;
	bool returnValue = true;
	struct stat inputFileStatus;
	struct utimbuf outputFileStatus;

	finput = fopen(sourceFullName.c_str(), "rb");
	if (finput == NULL)
	{
		return false;
	}
	foutput = fopen(targetFullName.c_str(), "wb");
	while (!feof(finput))
	{
		bytesRead = fread(buffer, sizeof(gchar), BufferSize, finput);
		if ((bytesRead < BufferSize) && (ferror(finput) > 0))
		{
			cout << "Error reading file " << sourceFullName.c_str() << "\n";
			fclose(finput);
			fclose(foutput);
			unlink(targetFullName.c_str());
			return false;
		}
		if (bytesRead > 0)
		{
			bytesWritten = fwrite(buffer, sizeof(gchar), bytesRead, foutput);
			if (bytesWritten != bytesRead)
			{
				cout << "Error writing to file " << targetFullName.c_str() << "\n";
				fclose(finput);
				fclose(foutput);
				unlink(targetFullName.c_str());
				return false;
			}
		}
	}
	fclose(finput);
	fclose(foutput);
	// Change the last modified time on the target file to match the source file.	
	stat(sourceFullName.c_str(), &inputFileStatus);
	outputFileStatus.actime = inputFileStatus.st_atime;
	outputFileStatus.modtime = inputFileStatus.st_mtime;
	utime(targetFullName.c_str(), &outputFileStatus);
#endif
	return true;
}

void FileOperations::MoveAFile(const gstring& source, const gstring& target)
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	MoveFileW(source.c_str(), target.c_str());
#else
	rename(source.c_str(), target.c_str());
#endif
}

#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
// This functions fills a caller-defined character buffer (pBuffer)
// of max length (cchBufferLength) with the human-readable error message
// for a Win32 error code (dwErrorCode).
// 
// Returns TRUE if successful, or FALSE otherwise.
// If successful, pBuffer is guaranteed to be NUL-terminated.
// On failure, the contents of pBuffer are undefined.
BOOL FileOperations::GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength)
{
	if (cchBufferLength == 0)
	{
		return FALSE;
	}

	DWORD cchMsg = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,  /* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
		dwErrorCode,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		pBuffer,
		cchBufferLength,
		NULL);
	return (cchMsg > 0);
}

#endif

void FileOperations::SleepMilliseconds(int delay)
{
#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	Sleep(delay);
#else
	usleep(delay * 1000);
#endif
}

void FileOperations::FileDateToString(INT64 fileTime, gstring& output)
{
	output.clear();
	struct tm localTime;
	time_t input = (time_t)fileTime;
	bool pm;
	int hour;	

#if defined WIN32 || defined _WIN32 || defined __CYGWIN__
	localtime_s(&localTime, &input);
#else
	localtime_r(&input, &localTime);
#endif
	output += to_gstring(localTime.tm_year+1900);
	output.append(TEXT("-"));
	if (localTime.tm_mon < 9)
	{
		output.append(TEXT("0"));
	}
	output += to_gstring(localTime.tm_mon+1);
	output.append(TEXT("-"));
	if (localTime.tm_mday < 10)
	{
		output.append(TEXT("0"));
	}
	output += to_gstring(localTime.tm_mday);
	output.append(TEXT(" "));
	hour = localTime.tm_hour;
	pm = (hour >= 12);
	if (hour > 12)
	{
		hour -= 12;
	}
	if (hour == 0)
	{
		hour = 12;
	}
	if (hour < 10)
	{
		output.append(TEXT("0"));
	}
	output += to_gstring(hour);
	output.append(TEXT(":"));
	if (localTime.tm_min < 10)
	{
		output.append(TEXT("0"));
	}
	output += to_gstring(localTime.tm_min);
	output.append(TEXT(":"));
	if (localTime.tm_sec < 10)
	{
		output.append(TEXT("0"));
	}
	output += to_gstring(localTime.tm_sec);
	output.append(pm ? TEXT(" PM") : TEXT(" AM"));
}
